﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;


namespace ExtremeLearningMachine
{
    /// <summary>
    /// Provedor de dados
    /// </summary>
    public class DataProvider
    {
        /// <summary>
        /// Base de dados
        /// </summary>
        public Data[] DataSet 
        {
            get;
            private set; 
        }

        public long DataSetLines;
        public long DataSetColumns;
        /// <summary>
        /// Número de entradas
        /// </summary>
        public long InputsN;
        /// <summary>
        /// Número de saídas
        /// </summary>
        public long OutputsN;

        public Data[] TrainSet;
        public long TrainSetLines;
        public long TrainSetColumns;

        public Data[] ValidationSet;
        public long ValidationSetLines;
        public long ValidationSetColumns;

        private Random _Random = null;
        public EDataType DataType;
        public int MaxClassificationClass = -1;
        public double[] MaxValue = null;
        public double[] MinValue = null;

        private bool CameFromFile = false;

        public DataProvider(Data[] dataSet, EDataType dataType)
        {
            DataType = dataType;
            DataSet = dataSet;
            DataSetLines = dataSet.Length;
            DataSetColumns = dataSet[0].Input.Length + dataSet[0].Output.Length;
            InputsN = dataSet[0].Input.Length;
            OutputsN = dataSet[0].Output.Length;

            MinValue = new double[DataSetColumns];
            MaxValue = new double[DataSetColumns];
        }

        public DataProvider(String filePath, EDataType dataType, Random random)
        {
            DataType = dataType;
            _Random = random;
           
            _ReadDataSetFromFile(filePath);
            CameFromFile = true;
        }

        public void ShuffleDataSet(long semente)
        {
            List<Data> list = new List<Data>();

            for (int i = 0; i < DataSet.Length; i++)
                list.Add(DataSet[i]);

            list.Shuffle(_Random);

            for (int i = 0; i < DataSet.Length; i++)
                DataSet[i] = list[i];
        }

        public void SplitData(long trainLines, long validationLines)
        {
            TrainSetLines = trainLines;
            ValidationSetLines = validationLines;

            ValidationSetColumns = TrainSetColumns = DataSetColumns; // numero de entradas + bias + saida		

            TrainSet = new Data[TrainSetLines];
            ValidationSet = new Data[ValidationSetLines];

            for (int i = 0; i < TrainSetLines; i++)
            {
                TrainSet[i] = DataSet[i];
                if (DataType == EDataType.Classification && CameFromFile)
                    TrainSet[i].Output = DataSet[i].Output[0].ConvertToArray(MaxClassificationClass);
            }

            for (long i = TrainSetLines, l = 0; i < TrainSetLines + ValidationSetLines; i++, l++)
            {
                ValidationSet[l] = DataSet[i];
                if (DataType == EDataType.Classification && CameFromFile)
                    ValidationSet[l].Output = DataSet[i].Output[0].ConvertToArray(MaxClassificationClass);
            }
        }
        public void SplitData()
        {
            TrainSetLines = (long)(DataSetLines * 0.75); // 75% dos dados		
            ValidationSetLines = (long)(DataSetLines * 0.25); // 25% dos dados

            ValidationSetColumns = TrainSetColumns = DataSetColumns; // numero de entradas + bias + saida		

            TrainSet = new Data[TrainSetLines];
            ValidationSet = new Data[ValidationSetLines];
            
            for (int i = 0; i < TrainSetLines; i++)
            {
                TrainSet[i] = DataSet[i];
                if (DataType == EDataType.Classification && CameFromFile)
                    TrainSet[i].Output = DataSet[i].Output[0].ConvertToArray(MaxClassificationClass);
            }

            for (long i = TrainSetLines, l = 0; i < TrainSetLines + ValidationSetLines; i++, l++)
            {
                ValidationSet[l] = DataSet[i];
                if (DataType == EDataType.Classification && CameFromFile)
                    ValidationSet[l].Output = DataSet[i].Output[0].ConvertToArray(MaxClassificationClass);
            }
        }

        public void NormalizeData(double inputA, double inputB, double outputA, double outputB)
        {
            for (int i = 0; i < DataSetLines; i++)
            {
                for (int j = 0; j < DataSet[0].Input.Length; j++)
                {
                    DataSet[i].Input[j] = _Normalize(DataSet[i].Input[j], inputA, inputB, j);
                    if (double.IsNaN(DataSet[i].Input[j]) || (DataSet[i].Input[j] < inputA || DataSet[i].Input[j] > inputB))
                        throw new Exception();
                }

                if (this.DataType == EDataType.Predction)
                {
                    for (int j = 0; j < DataSet[0].Output.Length; j++)
                    {
                        DataSet[i].Output[j] = _Normalize(DataSet[i].Output[j], outputA, outputB, (int)(j + InputsN));
                        if (double.IsNaN(DataSet[i].Output[j]) || (DataSet[i].Output[j] < outputA || DataSet[i].Output[j] > outputB ))
                            throw new Exception();
                    }
                }
            } 
        }

        public double DeNormalizeData(double value, double a, double b, int columnIndex)
        {
            double deNormalizade = 0;
            double cima = (value - a) * (MaxValue[columnIndex] - MinValue[columnIndex]);
            double baixo = (b - a) == 0 ? 0.000001 : (b - a);

            deNormalizade = (cima / baixo) + MinValue[columnIndex]; 
            return deNormalizade;
        }

        private double _Normalize(double value, double a, double b, int columnIndex)
        {
            double normalized = 0;
            double cima = (b - a) * (value - MinValue[columnIndex]);
            double baixo = (MaxValue[columnIndex] - MinValue[columnIndex]) == 0 ? 0.000001 : (MaxValue[columnIndex] - MinValue[columnIndex]);

            normalized = (cima / baixo) + a;
            return normalized;
        }

        private void _ReadDataSetFromFile(String filePath)
        {
            String line = null;
            using (StreamReader reader = new StreamReader(filePath))
            {
		        line = reader.ReadLine();
	            DataSetLines = long.Parse(line); // número de exemplosos
		
		        line = reader.ReadLine();
		        DataSetColumns = long.Parse(line); // representa o numero de entradas + numero de saidas
		
		        line = reader.ReadLine();
		        InputsN = long.Parse(line);; // numero de entradas da rede
		
		        line = reader.ReadLine();
		        OutputsN = long.Parse(line);; // numero de saidas da rede
		
		        DataSet = new Data[DataSetLines];
	
                MinValue = new double[DataSetColumns];
                for (int i = 0; i < MinValue.Length; i++)
                    MinValue[i] = double.MaxValue;

                MaxValue = new double[DataSetColumns];
                for (int i = 0; i < MaxValue.Length; i++)
                    MaxValue[i] = double.MinValue;

		        for(int i = 0; i < DataSetLines; i++)
                {
			        line = reader.ReadLine();

			        String[] values = line.Split(';');

			        DataSet[i] = new Data(new double[InputsN], new double[OutputsN]);
			
			        for (int j = 0; j < values.Length; j++) 
                    {
					    double val = double.Parse(values[j]);

                        if (double.IsNaN(val))
                            throw new Exception();

                        if (j >= InputsN)
                        {
                            DataSet[i].Output[j - InputsN] = val;
                            if (val > MaxClassificationClass)
                                MaxClassificationClass = (int)val;

                            if (val > MaxValue[j])
                                MaxValue[j] = val;

                            if (val < MinValue[j])
                                MinValue[j] = val;
                        }
                        else
                        {
                            DataSet[i].Input[j] = val;

                            if (this.DataType == EDataType.Predction)
                            {
                                if (val > MaxValue[j])
                                    MaxValue[j] = val;

                                if (val < MinValue[j])
                                    MinValue[j] = val;
                            }
                        }
			        }
		        }
            }
	    }

        public void SaveToFile(string path)
        {
            StreamWriter file = new System.IO.StreamWriter(path+"_train", true);
            
            string line = string.Empty;
            for (int i = 0; i < TrainSet.Length; i++)
			{
                line = string.Empty;
                for (int j = 0; j < TrainSet[0].Output.Length; j++)
                {
                    line += TrainSet[i].Output[j] + "; "; 
                }

                for (int j = 0; j < TrainSet[0].Input.Length; j++)
                {
                    line += TrainSet[i].Input[j] + "; ";
                }
                file.WriteLine(line); 
			}
            file.Close();

            file = new System.IO.StreamWriter(path+"_test", true);
            for (int i = 0; i < ValidationSet.Length; i++)
            {
                line = string.Empty;
                for (int j = 0; j < ValidationSet[0].Output.Length; j++)
                {
                    line += ValidationSet[i].Output[j] + "; ";
                }

                for (int j = 0; j < ValidationSet[0].Input.Length; j++)
                {
                    line += ValidationSet[i].Input[j] + "; ";
                }
                file.WriteLine(line);
            }
            file.Close();           
        }
    }

    public enum EDataType
    {
        Predction = 0,
        Classification = 1
    }
}
